package com.leyou.search.service.impl;

import com.leyou.common.dto.PageDTO;
import com.leyou.common.exceptions.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.entity.SearchParamDTO;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.dto.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.search.constants.SearchConstants.*;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    //操作elasticsearch的客户端Repository
    @Autowired
    private GoodsRepository repository;

    //Feign远程调用
    @Autowired
    private ItemClient itemClient;

    //创建索引库并设置映射
    @Override
    public void createIndexAndMapping() {
        // 删除已经存在的索引库
        try {
            log.info("尝试删除存在的索引库");
            repository.deleteIndex();
        } catch (Exception e) {
            log.warn("索引库不存在，无需删除！");
        }

        //创建索引库和映射
        log.info("尝试创建索引库");
        repository.createIndex(SOURCE_TEMPLATE);
    }

    //加载数据到索引库(从数据库查询商品信息保存到elasticsearch)
    @Override
    public void loadData() {
        int page = 1;
        int rows = 100;

        while (true) {
            log.info("开始导入第{}页数据",page);

            //1.查询商品spu
            PageDTO<SpuDTO> info = itemClient.querySpuByPage(page, rows, true, null, null, null);
            List<SpuDTO> spuDTOList = info.getItems();

            //2.把spu数据封装到goods
            List<Goods> goodsList = spuDTOList
                    .stream()
                    .map(spuDTO -> this.buildGoods(spuDTO))
                    .collect(Collectors.toList());

        /*List<Goods> goodsList = new ArrayList<>(spuDTOList.size());
        for (SpuDTO spuDTO : spuDTOList) {
            Goods goods = buildGoods(spuDTO);
            goodsList.add(goods);
        }*/

            //3.写入elasticsearch
            repository.saveAll(goodsList);
            log.info("导入第{}页数据成功！",page);

            //当前页大于等于总页数，表示是最后一页，跳出循环
            if (page >= info.getTotalPage()){
                break;
            }

            //4.分页
            page++;
        }
        log.info("全部数据导入成功，共{}页",page);
    }


    //异步  根据关键字自动补全查询
    @Override
    public Mono<List<String>> getSuggestion(String keyPrefix) {
        Mono<List<String>> suggestion = repository.suggestBySingleField(SUGGEST_FIELD, keyPrefix);
        return suggestion;
    }


    /*#搜素商品列表
#key from size sortBy desc
    GET /goods/_search
    {
        "_source": {
        "includes": ["id","title","prices","sold","image"]
    },
        "query": {
        "bool": {
            "must": [
            {"match": {
                "title": "手机"
            }}
      ]
        }
    },
        "from": 0,
            "size": 20,
            "sort": [
        {
            "prices": {
            "order": "desc"
        }
        }
  ],
        "highlight": {
        "fields": {"title": {}}
    }
    }*/
    //搜索商品列表
    @Override
    public Mono<PageInfo<Goods>> searchGoodsList(SearchParamDTO searchParamDTO) {

        //构建搜索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        // 1.source过滤
        builder.fetchSource(INCLUDE_SOURCE,EMPTY_SOURCE);
        // 2.query查询
        String key = searchParamDTO.getKey();
        //如果没有输入关键字
        if (StringUtils.isBlank(key)){
            throw new LyException(404,"抱歉，没有找到与“”相关的商品");
        }
        builder.query(QueryBuilders.boolQuery()
                .must(QueryBuilders.matchQuery(SEARCH_FIELD,key)));
        // 3.sort排序
        String sortBy = searchParamDTO.getSortBy();
        if (StringUtils.isNotBlank(sortBy)){
            //有sortBy字段，需要进行排序
            builder.sort(sortBy,searchParamDTO.getDesc()?SortOrder.DESC:SortOrder.ASC);
        }
        // 4.分页
        builder.from(searchParamDTO.getFrom()).size(searchParamDTO.getSize());
        // 5.高亮
        builder.highlighter(new HighlightBuilder()
                .field(HIGHLIGHT_FIELD)
                .preTags(PRE_TAG).postTags(POST_TAG));

        Mono<PageInfo<Goods>> mono = repository.queryBySourceBuilderForPageHighlight(builder);
        return mono;
    }

    // 商品上架，我们新增商品到索引库  mq消费者  mq生产者在SpuServiceImpl的updateSpuSaleable方法
    @Override
    public void saveItemBySpuId(Long spuId) {
        SpuDTO spuDTO = itemClient.queryGoodsById(spuId);
        //构建goods对象
        Goods goods = buildGoods(spuDTO);
        repository.save(goods);
    }

    // 商品下架，我们删除商品  mq消费者
    @Override
    public void deleteItemBySpuId(Long spuId) {
        repository.deleteById(spuId);
    }

    //构建Goods对象
    private Goods buildGoods(SpuDTO spuDTO) {
        Goods goods = new Goods();

        //1.商品标题，原有标题的基础上，拼接分类名称、分类品牌
        String title = spuDTO.getTitle()+spuDTO.getCategoryName()+spuDTO.getBrandName();

        //2.suggestion : 自动补全字段，包含：商品名称、分类名称、品牌名称
        Set<String> suggestion = new HashSet<>();
        suggestion.add(spuDTO.getName());
        suggestion.add(spuDTO.getBrandName());
        if (StringUtils.isNotBlank(spuDTO.getCategoryName())){
            System.out.println(spuDTO.getCategoryName());
            String[] split = spuDTO.getCategoryName().split("/");

            Collections.addAll(suggestion,split);
        }
        //3.规格参数specs,List<Map<String,Object>>
        //key是name和value，name的值是规格参数名称，value是规格参数的值

        //3.1 先查出spu的所有规格参数键值对，其中包含name和value属性
        List<SpecParamDTO> params = itemClient.querySpecsValues(spuDTO.getId(), true);
        //Goods的属性
        List<Map<String,Object>> specs = new ArrayList<>(params.size());
        //把SpecParamDTO变成Map<String,Object>
        for (SpecParamDTO param : params) {
            Map<String,Object> spec = new HashMap<>();
            spec.put("name",param.getName());
            spec.put("value",chooseSegment(param));//以范围形式存储参数值  例如：  CPU频率 1.5-2.0GHZ
            specs.add(spec);
        }

        //4.sku相关的数据。包括：图片、价格、销量

        //4.1 销量（多个sku销量之和）
        Long sold = 0L;//初始化

        List<SkuDTO> skuDTOList = spuDTO.getSkus();
        if (CollectionUtils.isEmpty(skuDTOList)){
            //上面的分页查询方法并没有查询sku,需另外查询
            skuDTOList = itemClient.querySkuBySpuId(spuDTO.getId());
        }

        //4.2 价格
        Set<Long> prices = new TreeSet<>();
        for (SkuDTO skuDTO : skuDTOList) {
            //销量之和
            sold+= skuDTO.getSold();
            //价格集合
            prices.add(skuDTO.getPrice());
        }

        //4.3 图片，取一张即可
        String image = StringUtils.substringBefore(skuDTOList.get(0).getImages(), ",");

        //5.封装goods
        goods.setUpdateTime(new Date());
        goods.setTitle(title);
        goods.setSuggestion(suggestion);
        goods.setSpecs(specs);
        goods.setSold(sold);
        goods.setPrices(prices);
        goods.setImage(image);
        goods.setId(spuDTO.getId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());

        return goods;
    }


    //以范围形式存储参数值  例如：  CPU频率 1.5-2.0GHZ
    private Object chooseSegment(SpecParamDTO p) {
        Object value = p.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if(!p.getNumeric() || StringUtils.isBlank(p.getSegments()) || value instanceof Collection){
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
}
